﻿function AzureOnlineTableRecordViewModel(context) {

    var self = this;

    var dispatcher = $({});

    var spinner = new SpinnerModel();

    var schema = ko.observable();
    var table = ko.observable('');
    var record = ko.observable();
    var originalRecord = ko.observable(null);

    var isNew = ko.computed(function () {
        var rec = record();
        if (!rec) return true;
        var id = rec.id();
        return !id;
    });

    var fields = ko.observableArray([]);
    var knownFields = ko.observableArray([]);

    ko.computed(function () {
        var tableSchema = schema();
        if (tableSchema) {
            table(tableSchema.table());
            var flds = tableSchema.fields();
            knownFields(flds);
        } else {
            table('');
            knownFields([]);
        }
    });

    function init() {
        var rec = record();
        var flds = rec.fields();
        for (var i = 0; i < flds.length; i++) {
            var fld = flds[i];
            var name = fld.name();
            switch (name) {
                case 'PartitionKey':
                case 'RowKey':
                case 'Timestamp':
                    fld.system(true);
                    break;
            }
        }
    }

    function forTable(tableSchema) {
        schema(tableSchema);
        var rec = tableSchema.createRecord();
        record(rec);
        originalRecord(null);
        init();
    }

    function forRecord(tableSchema, rec) {
        schema(tableSchema);
        originalRecord(rec);
        record(rec.clone());
        init();
    }

    ko.computed(function () {
        var rec = record();
        if (rec) {
            var flds = rec.fields();
            var lastField = flds.length > 0 ? flds[flds.length - 1] : null;
            if (lastField) {
                var name = lastField.name();
                if (!utils.isNullOrWhiteSpace(name)) {
                    var fld = new AzureOnlineTableRecordField();
                    fld.name('');
                    fld.name();
                    rec.fields.push(fld);
                }
            }
        }
    });

    function submit() {
        var api = context.api();
        var rec = record();
        rec.id();
        rec.updated(new Date());
        var atomXml = rec.getAtomXml();
        if (!isNew()) {
            var original = originalRecord();
            spinner.enter();
            api.tables.updateEntity({
                table: table(),
                partitionKey: original.partitionKey(),
                rowKey: original.rowKey(),
                content: atomXml,
                success: function () {
                    spinner.leave();
                    original.setAtomXml(atomXml);
                    dispatcher.trigger('success');
                },
                error: function () {
                    spinner.leave();
                    return 'notify';
                }
            });
        } else {
            spinner.enter();
            api.tables.insertEntity({
                table: table(),
                content: atomXml,
                success: function () {
                    spinner.leave();
                    dispatcher.trigger('success');
                },
                error: function () {
                    spinner.leave();
                    return 'notify';
                }
            });
        }
    }

    self.record = record;
    self.isNew = isNew;
    self.fields = fields;
    self.submit = submit;
    self.forTable = forTable;
    self.forRecord = forRecord;
    self.addOnSuccess = function (handler) {
        dispatcher.bind('success', handler);
    };
}

function AzureOnlineTableRecordField() {

    var self = this;

    var name = ko.observable();
    var edmType = ko.observable();
    var value = ko.observable();
    var system = ko.observable(false);

    var canChangeName = ko.computed(function () {
        if (!system()) return true;
        var fld = name();
        switch (fld) {
            case 'PartitionKey':
            case 'RowKey':
            case 'Timestamp':
                return false;
        }
        return true;
    });

    var canChangeType = ko.computed(function () {
        if (!system()) return true;
        var fld = name();
        switch (fld) {
            case 'PartitionKey':
            case 'RowKey':
            case 'Timestamp':
                return false;
        }
        return true;
    });

    var canChangeValue = ko.computed(function () {
        if (!system()) return true;
        var fld = name();
        switch (fld) {
            case 'PartitionKey':
            case 'RowKey':
                return true;
        }
        return false;
    });

    self.name = name;
    self.edmType = edmType;
    self.value = value;
    self.system = system;

    self.canChangeName = canChangeName;
    self.canChangeType = canChangeType;
    self.canChangeValue = canChangeValue;
}

function AzureOnlineTableRecord($initialInfo) {

    var self = this;

    var id = ko.observable('');

    var partitionKey = ko.observable('');

    var rowKey = ko.observable('');

    this.timestamp = ko.observable('');

    var updated = ko.observable(null);

    var fields = ko.observableArray([]);

    var dateFormat = 'yyyy-MM-ddTHH:mm:ss.ffffffZ';
    var dateFormatRound = 'yyyy-MM-ddTHH:mm:ss';
    var dateFormatRoundZ = 'yyyy-MM-ddTHH:mm:ssZ';

    function parseDate(str) {
        var formatter = new DateTimeFormatter();
        var res = formatter.parseDateTime(str, dateFormat);
        if (res) return res;
        res = formatter.parseDateTime(str, dateFormatRound);
        return res;
    }

    function formatDate(dt) {
        if (!dt) return '';
        var formatter = new DateTimeFormatter();
        return formatter.dateFormat(dt, dateFormatRoundZ);
    }

    this.values = {};

    function set(key, value) {
        switch (key) {
            case 'PartitionKey':
                partitionKey(value);
                break;
            case 'RowKey':
                rowKey(value);
                break;
            case 'Timestamp':
                self.timestamp(value);
                break;
        }
        self.values[key] = value;
        var fld = findField(key);
        if (fld) {
            fld.value(value);
        }
    }

    function get(key) {
        return self.values[key];
    }
    
    function findField(name) {
        var flds = fields();
        for (var i = 0; i < flds.length; i++) {
            var fld = flds[i];
            if (fld.name() == name) return fld;
        }
        return null;
    }

    function escape(str) {
        return $('<div/>').text(str).html();
    }

    function getAtomXml() {
        var res = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>\r\n';
        res += '<entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">\r\n';
        res += '<title />\r\n';
        res += '<updated>' + formatDate(updated()) + '</updated>\r\n';
        res += '<author>\r\n';
        res += '<name />\r\n';
        res += '</author>\r\n';
        var ident = id();
        if (ident) {
            res += '<id>' + ident + '</id>\r\n';
        } else {
            res += '<id />\r\n';
        }
        res += '<content type="application/xml">\r\n';
        res += '<m:properties>\r\n';
        var flds = fields();
        for (var i = 0; i < flds.length; i++) {
            var fld = flds[i];
            var name = fld.name();
            if (!utils.isNullOrWhiteSpace(name)) {
                var val = fld.value();
                var type = fld.edmType();
                res += '<d:' + name;
                if (!(!type || type == 'string' || type == 'Edm.String')) {
                    res += ' m:type="' + type + '"';
                }
                res += '>' + escape(val) + '</d:' + name + '>\r\n';
            }
        }
        res += '</m:properties>\r\n';
        res += '</content>\r\n';
        res += '</entry>';
        return res;
    }

    function setAtomXml(atomXml) {
        var $atom = $($.parseXML(atomXml));
        self.setInfo($atom);
    }

    function clone() {
        var atomXml = getAtomXml();
        var $atom = $($.parseXML(atomXml));
        var cloned = new AzureOnlineTableRecord($atom);
        return cloned;
    }

    this.setInfo = function ($info) {
        var $id = $info.find('id');
        id($id.text());

        var $updated = $info.find('updated');
        var updatedRaw = $updated.text();
        var upd = parseDate(updatedRaw);
        updated(upd);

        var $content = $info.find('content');
        var $props = $content.children().filterNode('m:properties');

        var newFields = [];
        var children = $props.children();
        for (var i = 0; i < children.length; i++) {
            var child = children[i];
            var $child = $(child);
            var name = child.localName;
            var value = $child.text();
            var type = $child.attr('m:type');
            var field = new AzureOnlineTableRecordField();
            field.name(name);
            field.edmType(type);
            field.value(value);
            newFields.push(field);
            set(name, value);
        }
        fields(newFields);
    };

    if ($initialInfo) {
        this.setInfo($initialInfo);
    }

    self.id = id;
    self.partitionKey = partitionKey;
    self.rowKey = rowKey;
    self.updated = updated;
    self.getAtomXml = getAtomXml;
    self.setAtomXml = setAtomXml;
    self.clone = clone;
    self.findField = findField;
    self.set = set;
    self.get = get;
    self.fields = fields;
}

ko.bindingHandlers.recordValue = {
    init: function (element, valueAccessor) {
        var $elem = $(element);
        $elem.bind('change', function () {
            var val = valueAccessor();
            val($elem.val());
        });
    },
    update: function (element, valueAccessor) {
        var $elem = $(element);
        var val = ko.utils.unwrapObservable(valueAccessor());
        $elem.val(val);
    }
};